﻿// file:	kdbgout.c
#include "pch.h"
#include "sbcs.h"
#include "extend/sberror.h"
#include <signal.h>

#if _MSC_VER && _SB_WINDOWS_DESKTOP_
#include <dbghelp.h>
#endif

#if _SB_ANDROID_
#include <android/log.h>	// 링크에 liblog.so 필요
#endif

#define USE_OLV_PREFIX	1
#define USE_DBG_LOCK	1

#if USE_DBG_LOCK
#define DBG_LOCK			K_LOCK(k_impl_dbgout.lock)
#define DBG_UNLOCK			K_UNLOCK(k_impl_dbgout.lock)
#else
#define DBG_LOCK			kp_lock()
#define DBG_UNLOCK			kp_unlock()
#endif


//////////////////////////////////////////////////////////////////////////
// 출력 레벨

/**
 * 출력 레벨 접두사.
 * @param	v	KOLV.
 * @return	접두사.
 */
char k_lvprefix(kint v)
{
	static char _k_olv_prefixs[] =
	{
		'A',
		'N',
		'R',
		'I',
		'W',
		'E',
		'C',
		'F',
		'U'
	};
	return _k_olv_prefixs[(ksize_t)v < K_COUNTOF(_k_olv_prefixs) ? (ksize_t)v : K_COUNTOF(_k_olv_prefixs) - 1];
}

/**
 * 출력 레벨 문자열.
 * @param	v	KOLV.
 * @return	문자열.
 */
const char* k_lvname(kint v)
{
	static const char* _k_olv_names[] =
	{
		"all",
		"internal",
		"result",
		"info",
		"warning",
		"error",
		"critical",
		"fatal",
		"unknown"
	};
	return _k_olv_names[(ksize_t)v < K_COUNTOF(_k_olv_names) ? (ksize_t)v : K_COUNTOF(_k_olv_names) - 1];
}

// 안드로이드 레벨
#if _SB_ANDROID_
static kint k_lvandroid(kint v)
{
	static const kint s_andid[] =
	{
		ANDROID_LOG_VERBOSE,	// ALL
		ANDROID_LOG_DEBUG,		// INTERNAL
		ANDROID_LOG_VERBOSE,	// RESULT
		ANDROID_LOG_INFO,		// INFO
		ANDROID_LOG_WARN,		// WARNING
		ANDROID_LOG_ERROR,		// ERROR
		ANDROID_LOG_ERROR,		// CRITICAL
		ANDROID_LOG_FATAL,		// FATAL
		ANDROID_LOG_INFO,		// NONE
	};

	return (ksize_t)v < K_COUNTOF(s_andid) ? s_andid[v] : s_andid[0];
}
#endif


//////////////////////////////////////////////////////////////////////////
// 출력 및 디버그

//
static struct kImplDbgOut
{
	kint				level;
	kcham				isdbg;
	kcham				ischar;

	kchar				tag[32];

#if USE_DBG_LOCK
	kLock				lock;
#endif

#if _SB_WINDOWS_
	HANDLE				handle;
#else
	FILE*               fp;
#endif
} k_impl_dbgout =
{
	KLV_INFO,
	TRUE,
	TRUE,

	"sb",

#if USE_DBG_LOCK
	KLOCK_INIT,
#endif

	NULL,
};

// 
void _k_dbgout_init(void)
{
#if _SB_WINDOWS_
#	if _SB_WINDOWS_DESKTOP_
	k_impl_dbgout.handle = GetStdHandle(STD_OUTPUT_HANDLE);
#	else
	k_impl_dbgout.ischar = FALSE;
	k_impl_dbgout.handle = NULL;
#	endif
#else
#	if !_SB_MOBILE_
	k_impl_dbgout.fp = stdout;
#	else
	k_impl_dbgout.ischar = FALSE;
	k_impl_dbgout.fp = NULL;
#	endif
#endif
}

// 
void _k_dbgout_disp(void)
{
#if _SB_WINDOWS_
	if (k_impl_dbgout.handle != NULL && !k_impl_dbgout.ischar)
		CloseHandle(k_impl_dbgout.handle);
#else
	if (k_impl_dbgout.fp != NULL && !k_impl_dbgout.ischar)
		fclose(k_impl_dbgout.fp);
#endif
}

// 콘솔 출력
static void _k_dbg_console_str(const char* s, kint len, kcham dbgout)
{
	DBG_LOCK;
#if _SB_WINDOWS_
	if (k_impl_dbgout.handle != NULL)
	{
		DWORD wtn, dw = (DWORD)(len <= 0 ? k_strlen(s) : len);

#if _SB_WINDOWS_DESKTOP_
		if (!k_impl_dbgout.ischar)
			WriteFile(k_impl_dbgout.handle, s, dw, &wtn, NULL);
		else if (WriteConsoleA(k_impl_dbgout.handle, s, dw, &wtn, NULL) == 0)
			WriteFile(k_impl_dbgout.handle, s, dw, &wtn, NULL);
#else
		WriteFile(k_impl_dbgout.handle, s, dw, &wtn, NULL);
#endif
	}
#else
	if (k_impl_dbgout.fp != NULL)
		fputs(s, k_impl_dbgout.fp);
#endif
	DBG_UNLOCK;

	if (dbgout && k_impl_dbgout.isdbg)
	{
#if _SB_WINDOWS_
		OutputDebugStringA(s);
#elif _SB_ANDROID_
		__android_log_print(ANDROID_LOG_VERBOSE, k_impl_dbgout.tag, "%s", s);
#endif
	}
}

// 문자 출력
static void _k_dbg_console_char(int ch, kcham dbgout)
{
	DBG_LOCK;
#if _SB_WINDOWS_
	if (k_impl_dbgout.handle != NULL)
	{
		DWORD wtn;

#if _SB_WINDOWS_DESKTOP_
		if (!k_impl_dbgout.ischar)
			WriteFile(k_impl_dbgout.handle, &ch, 1, &wtn, NULL);
		else if (WriteConsoleA(k_impl_dbgout.handle, &ch, 1, &wtn, NULL) == 0)
			WriteFile(k_impl_dbgout.handle, &ch, 1, &wtn, NULL);
#else
		WriteFile(k_impl_dbgout.handle, &ch, 1, &wtn, NULL);
#endif
	}
#else
	if (k_impl_dbgout.fp != NULL)
		fputc(ch, k_impl_dbgout.fp);
#endif
	DBG_UNLOCK;

	if (dbgout && k_impl_dbgout.isdbg)
	{
#if _SB_WINDOWS_
		char sz[2];
		sz[0] = (char)ch;
		sz[1] = '\0';
		OutputDebugStringA(sz);
#elif _SB_ANDROID_
		__android_log_print(ANDROID_LOG_VERBOSE, k_impl_dbgout.tag, "%c", ch);
#endif
	}
}

// 문자열 출력
#if _SB_WINDOWS_ 
static void _k_dbg_console_windows(const kwchar* s, kint len, kcham dbgout)
{
	DBG_LOCK;
	if (k_impl_dbgout.handle != NULL)
	{
		DWORD wtn, dw = (DWORD)(len <= 0 ? k_wcslen(s) : len);

#if _SB_WINDOWS_DESKTOP_
		if (!k_impl_dbgout.ischar)
			WriteFile(k_impl_dbgout.handle, s, dw * sizeof(kwchar), &wtn, NULL);
		else if (WriteConsole(k_impl_dbgout.handle, s, dw, &wtn, NULL) == 0)
			WriteFile(k_impl_dbgout.handle, s, dw * sizeof(kwchar), &wtn, NULL);
#else
		WriteFile(k_impl_dbgout.handle, s, dw * sizeof(kwchar), &wtn, NULL);
#endif
	}
	DBG_UNLOCK;

	if (dbgout && k_impl_dbgout.isdbg)
		OutputDebugString(s);
}
#endif

// 출력
static void _k_dbg_console_trace(int level, const char* cls, const char* text, kcham dbgout)
{
#if USE_OLV_PREFIX
	char cp = k_lvprefix(level);
#else
	const char* lv = k_lvname(level);
#endif
	const char* fmt;
	char* buf;
	int size;

	if (!cls)
		cls = k_impl_dbgout.tag;

#if USE_OLV_PREFIX
	fmt = "[%c/%s] %s\n";
	size = k_snprintf(NULL, 0, fmt, cp, cls, text);
	buf = k_local_new(size + 1, char);
	k_snprintf(buf, size + 1, fmt, cp, cls, text);
#else
	fmt = "[%s.%s] %s\n";
	size = k_snprintf(NULL, 0, fmt, cls, lv, text);
	buf = k_local_new(size + 1, char);
	k_snprintf(buf, size + 1, fmt, cls, lv, text);
#endif

	_k_dbg_console_str(buf, size, FALSE);

	if (dbgout && k_impl_dbgout.isdbg)
	{
#if _SB_WINDOWS_
		OutputDebugStringA(buf);
#elif _SB_ANDROID_
		buf[size-1] = '\0';
		__android_log_print(k_lvandroid(level), k_impl_dbgout.tag, "%s", buf);
#endif
	}

	//
	if (level == KLV_FATAL)
	{
#if _SB_WINDOWS_DESKTOP_
		MessageBoxA(NULL, text, NULL, MB_ICONERROR | MB_SETFOREGROUND);
#endif

		if (k_candebug())
			k_breakpoint();

		abort();
	}
}


//////////////////////////////////////////////////////////////////////////
// 아웃풋

/**
 * 문자 쓰기.
 * @param	ch	문자.
 */
void k_output(kint ch)
{
	k_init();

	_k_dbg_console_char(ch, FALSE);
}

/**
 * 문자열 출력.
 * @param	s	문자열.
 */
void k_outputs(const char* s)
{
	k_init();

	_k_dbg_console_str(s, 0, FALSE);
}

/**
 * 문자열 출력, 포맷.
 * @param	fmt	포맷 문자열.
 */
void k_outputf(const char* fmt, ...)
{
	int ret;
	char* buf;
	va_list va;

	k_init();

	va_start(va, fmt);
	ret = k_vsnprintf(NULL, 0, fmt, va);
	va_end(va);

	buf = k_local_new(ret + 1, char);
	va_start(va, fmt);
	k_vsnprintf(buf, ret + 1, fmt, va);
	va_end(va);

	_k_dbg_console_str(buf, ret, FALSE);
}

/**
 * UTF-8 문자열 출력.
 * @param	s	문자열.
 */
void k_outputs_utf8(const char* s)
{
#if _SB_WINDOWS_
	kwchar* uni;
	kint ret;

	k_init();

	ret = (kint)k_conv_utf8_to_utf16(NULL, 0, s, 0);
	uni = k_local_new(ret + 1, kwchar);
	k_conv_utf8_to_utf16(uni, ret + 1, s, 0);

	_k_dbg_console_windows(uni, ret, FALSE);
#else
	k_outputs(s);
#endif
}

/**
 * UTF-8 문자열 출력, 포맷.
 * @param	fmt	포맷 문자열.
 */
void k_outputf_utf8(const char* fmt, ...)
{
	int ret;
	char* buf;
	va_list va;
#if _SB_WINDOWS_
	kwchar* uni;
#endif

	k_init();

	va_start(va, fmt);
	ret = k_vsnprintf(NULL, 0, fmt, va);
	va_end(va);

	buf = k_local_new(ret + 1, char);
	va_start(va, fmt);
	k_vsnprintf(buf, ret + 1, fmt, va);
	va_end(va);

#if _SB_WINDOWS_
	ret = (kint)k_conv_utf8_to_utf16(NULL, 0, buf, 0);
	uni = k_local_new(ret + 1, kwchar);
	k_conv_utf8_to_utf16(uni, ret + 1, buf, 0);

	_k_dbg_console_windows(uni, ret, FALSE);
#else
	_k_dbg_console_str(buf, ret, FALSE);
#endif
}


//////////////////////////////////////////////////////////////////////////
// 출력

/**
 * 출력 레벨을 얻는다.
 * @return	출력 레벨.
 */
kint k_getlv(void)
{
	return k_impl_dbgout.level;
}

/**
 * 출력 레벨을 쓴다.
 * @param	value	넣을 출력 레벨.
 */
void k_setlv(kint value)
{
	k_return_if_fail((kuint)value <= KLV_NONE);
	k_init();
	k_impl_dbgout.level = value;
}

/**
 * 현재 설정된 태그를 얻는다.
 * @return	현재의 태그.
 */
const char* k_gettag(void)
{
	return k_impl_dbgout.tag;
}

/**
 * 태그를 설정한다.
 * @param	str_len_32	태그 문자열, 최대 길이는 32글자.
 */
void k_settag(const char* str_len_32)
{
	k_init();

	DBG_LOCK;

	if (str_len_32 && *str_len_32)
		k_strncpy(k_impl_dbgout.tag, str_len_32, 32 - 1);
	else
	{
		k_impl_dbgout.tag[0] = 's';
		k_impl_dbgout.tag[1] = 'b';
		k_impl_dbgout.tag[2] = '\0';
	}

	DBG_UNLOCK;
}

/**
 * 디버그 출력, 글자.
 * @param	ch	문자.
 */
void k_mesg(int ch)
{
	k_init();

	_k_dbg_console_char(ch, TRUE);
}

/**
 * 디버그 출력.
 * @param	text	문자열.
 */
void k_mesgs(const char* text)
{
	k_init();

	_k_dbg_console_str(text, 0, TRUE);
}

/**
 * 디버그 출력, 포맷.
 * @param	fmt	포맷 문자열.
 */
void k_mesgf(const char* fmt, ...)
{
	char* buf;
	int len;
	va_list va;

	k_init();

	va_start(va, fmt);
	len = k_vsnprintf(NULL, 0, fmt, va);
	va_end(va);

	buf = k_local_new(len + 1, char);
	va_start(va, fmt);
	k_vsnprintf(buf, len + 1, fmt, va);
	va_end(va);

	_k_dbg_console_str(buf, len, TRUE);
}

/**
 * 디버그 출력, UTF-8.
 * @param	s	문자열.
 */
void k_mesgs_utf8(const char* s)
{
#if _SB_WINDOWS_
	kwchar* uni;
	int len;

	k_init();

	len = (kint)k_conv_utf8_to_utf16(NULL, 0, s, 0);
	uni = k_local_new(len + 1, kwchar);
	k_conv_utf8_to_utf16(uni, len + 1, s, 0);
	_k_dbg_console_windows(uni, len, TRUE);
#else
	k_mesgs(s);
#endif
}

/**
 * 디버그 출력, 포맷, UTF-8.
 * @param	fmt	포맷 문자열.
 */
void k_mesgf_utf8(const char* fmt, ...)
{
	char* buf;
	int len;
	va_list va;
#if _SB_WINDOWS_
	kwchar* uni;
#endif

	k_init();

	va_start(va, fmt);
	len = k_vsnprintf(NULL, 0, fmt, va);
	va_end(va);

	buf = k_local_new(len + 1, char);
	va_start(va, fmt);
	k_vsnprintf(buf, len + 1, fmt, va);
	va_end(va);

#if _SB_WINDOWS_
	len = (kint)k_conv_utf8_to_utf16(NULL, 0, buf, 0);
	uni = k_local_new(len + 1, kwchar);
	k_conv_utf8_to_utf16(uni, len + 1, buf, 0);
	_k_dbg_console_windows(uni, len, TRUE);
#else
	_k_dbg_console_str(buf, len, TRUE);
#endif
}

/**
 * 트레이스 텍스트.
 * @param	level	출력 레벨.
 * @param	cls  	클래스 이름.
 * @param	text 	텍스트.
 */
void k_debug_traces(kint level, const char* cls, const char* text)
{
	k_init();

	_k_dbg_console_trace(level, cls, text, TRUE);
}

/**
 * 트레이스 텍스트, 포맷.
 * @param	level	출력 레벨.
 * @param	cls  	클래스 이름.
 * @param	fmt  	포맷 문자열.
 */
void k_debug_tracef(kint level, const char* cls, const char* fmt, ...)
{
	char* buf;
	int len;
	va_list va;

	k_init();

	va_start(va, fmt);
	len = k_vsnprintf(NULL, 0, fmt, va);
	va_end(va);

	buf = k_local_new(len + 1, char);
	va_start(va, fmt);
	k_vsnprintf(buf, len + 1, fmt, va);
	va_end(va);

	_k_dbg_console_trace(level, cls, buf, TRUE);
}

/**
 * 오류 코드만 이용하는 빠른 트레이스 메시지.
 * @param	level   	출력 레벨.
 * @param	cls			클래스 이름.
 * @param	error   	오류 번호.
 * @param	filename	파일의 파일 이름.
 * @param	line		파일의 줄 번호.
 */
void k_debug_tracep(kint level, const char* cls, kint error, const char* filename, kint line)
{
	char tmp[128];
	const char* msg;

	k_init();

	// 오류 메시지
	if (error != 0)
		msg = k_errmsg(error);
	else
	{
		// 시스템 오류로 친다
		k_syserr(tmp, 128 - 1, 0);
		msg = tmp;
	}

	// 메시지 만들고
	if (!filename)
		_k_dbg_console_trace(level, cls, msg, TRUE);
	else
	{
		// 파일 이름까지 출력
		// 오류 메시지+파일이름까지 저장하긴 하는데... MAX_PATH로 함
		char sz[K_MAX_PATH];
		k_snprintf(sz, K_MAX_PATH - 1, "%s (%s:%d)", msg, filename, line);
		_k_dbg_console_trace(level, cls, sz, TRUE);
	}
}

/**
 * 메시지 출력하는 트레이스 메시지.
 * @param	level   	출력 레벨.
 * @param	cls			클래스 이름.
 * @param	error   	오류 번호.
 * @param	text		메시지 내용.
 * @param	filename	파일의 파일 이름.
 * @param	line		파일의 줄 번호.
 */
void k_debug_tracet(kint level, const char* cls, kint error, const char* text, const char* filename, kint line)
{
	k_init();

	// 잠만, 이거 error왜 안썼지
	K_NOUSE(error);

	// 오류 메시지
	if (!text)
		text = k_errmsg(KERR_UNKNOWN);

	// 메시지 만들고
	if (!filename)
		_k_dbg_console_trace(level, cls, text, TRUE);
	else
	{
		// 파일 이름까지 출력
		// 오류 메시지+파일이름까지 저장하긴 하는데... MAX_PATH로 함
		char sz[K_MAX_PATH];
		k_snprintf(sz, K_MAX_PATH - 1, "%s (%s:%d)", text, filename, line);
		_k_dbg_console_trace(level, cls, sz, TRUE);
	}
}

/**
 * 오류처리용 assert.
 * @param	expr		표현.
 * @param	mesg		오류 내용.
 * @param	filename	파일의 파일 이름.
 * @param	line		파일의 줄 번호.
 * @return	kint.
 */
kint k_debug_assert(const char* expr, const char* mesg, const char* filename, kint line)
{
	char* buf;
	int len;

	k_init();

	len = k_snprintf(NULL, 0, "filename=\"%s\", line=%d", filename, line);
	buf = k_local_new(len + 1, char);
	k_snprintf(buf, len + 1, "filename=\"%s\", line=%d", filename, line);
	_k_dbg_console_trace(KLV_CRITICAL, "k.assert", buf, TRUE);

	if (mesg)
		_k_dbg_console_trace(KLV_NONE, "k.assert", mesg, TRUE);

	if (expr)
		_k_dbg_console_trace(KLV_NONE, "k.assert", expr, TRUE);

	return 0;
}

/**
 * 오류처리용 메시지 출력.
 * @param	ew			참이면 오류, 거짓이면 경고.
 * @param	col			파일의 열 번호.
 * @param	line		파일의 줄 번호.
 * @param	filename	파일의 파일 이름.
 * @param	code		코드 번호.
 * @param	pass		패스 이름.
 * @param	text		출력 메시지.
 * @return	반드시 0을 반환.
 */
kint k_debug_error(kcham ew, kint col, kint line, const char* filename, kint code, const char* pass, const char* text)
{
	char codemesg[24];
	char* buf;
	int len;

	k_init();

	if (filename == NULL)
		filename = "unknown";

	if (text == NULL)
	{
		text = codemesg;
		k_snprintf(codemesg, 24 - 1, "code=%X", code);
	}

#if _SB_WINDOWS_
	// ms 스타일
	len = k_snprintf(NULL, 0, "%s(%d:%d) : %s %d: %s\n", filename, col, line, (ew) ? "error" : "warning", code, text);
	buf = k_local_new(len + 1, char);
	k_snprintf(buf, len + 1, "%s(%d:%d) : %s %d: %s\n", filename, col, line, (ew) ? "error" : "warning", code, text);
	_k_dbg_console_str(buf, len, TRUE);
	/**/
	pass;
#else
	// 유닉스 스타일
	len = k_snprintf(NULL, 0, "in %s(%d:%d), [%d] %s\n", filename, col, line, code, text);
	buf = k_local_new(len + 1, char);
	k_snprintf(buf, len + 1, "in %s(%d:%d), [%d] %s\n", filename, col, line, code, text);
	_k_dbg_console_trace(ew ? KLV_ERROR : KLV_WARNING, pass ? pass : "k", buf, TRUE);
#endif

	return 0;
}

/**
 * 버퍼에 에러메시지 넣기, [ST]ore [B]uffer by [M]essage.
 * @param [반환]	retbuf	(널값이 아닌) 반환 버퍼.
 * @param	size	  	버퍼의 크기.
 * @param	ew		  	참이면 오류, 거짓이면 경고.
 * @param	col		  	파일의 열 번호.
 * @param	line	  	파일의 줄 번호.
 * @param	filename  	파일의 파일 이름.
 * @param	code	  	코드 번호.
 * @param	pass	  	패스 이름.
 * @param	text	  	출력 메시지.
 * @return	만들어진 문자열의 길이.
 */
KAPI kint k_debug_store_error(char* retbuf, kint size, kcham ew, kint col, kint line, const char* filename, kint code, const char* pass, const char* text)
{
	char codemesg[24];

	k_init();

	if (filename == NULL)
		filename = "unknown";

	if (text == NULL)
	{
		text = codemesg;
		k_snprintf(codemesg, 24 - 1, "code=%X", code);
	}

	// 이건 왜 안썼남
	K_NOUSE(pass);

#if _SB_WINDOWS_
	// ms 스타일
	return k_snprintf(retbuf, size, "%s(%d:%d) : %s %d: %s\n", filename, col, line, (ew) ? "error" : "warning", code, text);
	/**/
	pass;
#else
	// 유닉스 스타일
	return k_snprintf(retbuf, size, "in %s(%d:%d), [%d] '%s' as %s\n", filename, col, line, code, text, (ew) ? "error" : "warning");
#endif
}

/**
 * HALT 메시지.
 * @param	cls	클래스 이름.
 * @param	msg	출력할 메시지.
 */
void k_debug_halt(const char* cls, const char* msg)
{
	const char* fmt;
	char* buf;
	int len;

	k_init();

	if (!cls)
		cls = k_impl_dbgout.tag;

	if (msg)
	{
		fmt = "HALT [%s] %s\n";
		len = k_snprintf(NULL, 0, fmt, cls, msg);
		buf = k_local_new(len + 1, char);
		k_snprintf(buf, len + 1, fmt, cls, msg);
	}
	else
	{
		fmt = "HALT [%s]\n";
		len = k_snprintf(NULL, 0, fmt, cls);
		buf = k_local_new(len + 1, char);
		k_snprintf(buf, len + 1, fmt, cls);
	}

	_k_dbg_console_str(buf, len, TRUE);

	if (k_candebug())
		k_breakpoint();

	k_exit(255);
}

/**
 * 출력을 다른 곳으로 전환. 파일이나 표준 입출력 등.
 * @param	filename	파일의 이름.
 */
void k_redirect(const char* filename)
{
	k_init();

	DBG_LOCK;

#if _SB_WINDOWS_
	if (!k_impl_dbgout.ischar)
		CloseHandle(k_impl_dbgout.handle);

	if (!filename || k_strieqv(filename, "stdout") || k_strieqv(filename, "stdin"))
	{
		k_impl_dbgout.ischar = TRUE;
#if _SB_WINDOWS_DESKTOP_
		k_impl_dbgout.handle = GetStdHandle(STD_OUTPUT_HANDLE);
#else
		k_impl_dbgout.handle = NULL;
#endif
	}
	else if (k_strieqv(filename, "stderr"))
	{
		k_impl_dbgout.ischar = TRUE;
#if _SB_WINDOWS_DESKTOP_
		k_impl_dbgout.handle = GetStdHandle(STD_ERROR_HANDLE);
#else
		k_impl_dbgout.handle = NULL;
#endif
	}
	else
	{
		HANDLE h;
		LARGE_INTEGER ll;
		kwchar wz[260];
#if !_SB_WINDOWS_DESKTOP_
		CREATEFILE2_EXTENDED_PARAMETERS cep;
#endif

		k_conv_utf8_to_utf16(wz, 260 - 1, filename, 0);

#if _SB_WINDOWS_DESKTOP_
		h = CreateFile(wz, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
#else
		k_zero_1(&cep);
		cep.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
		h = CreateFile2(wz, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_ALWAYS, &cep);
#endif

		if (h == INVALID_HANDLE_VALUE)
		{
			// 실패하면 표준 출력으로
			k_impl_dbgout.ischar = TRUE;
#if _SB_WINDOWS_DESKTOP_
			k_impl_dbgout.handle = GetStdHandle(STD_OUTPUT_HANDLE);
#else
			k_impl_dbgout.handle = NULL;
#endif
		}
		else
		{
			k_impl_dbgout.ischar = FALSE;
			k_impl_dbgout.handle = h;

			ll.QuadPart = 0;
			SetFilePointerEx(h, ll, &ll, FILE_END);
		}
	}
#else
	if (k_impl_dbgout.fp != NULL && !k_impl_dbgout.ischar)
		fclose(k_impl_dbgout.fp);

	if (!filename || k_strieqv(filename, "stdout") || k_strieqv(filename, "stdin"))
	{
		k_impl_dbgout.ischar = TRUE;
#if !_SB_MOBILE_
		k_impl_dbgout.fp = stdout;
#else
		k_impl_dbgout.fp = NULL;
#endif
	}
	else if (k_strieqv(filename, "stderr"))
	{
		k_impl_dbgout.ischar = TRUE;
#if !_SB_MOBILE_
		k_impl_dbgout.fp = stderr;
#else
		k_impl_dbgout.fp = NULL;
#endif
	}
	else
	{
		FILE* fp = fopen(filename, "a+");

		if (!fp)
		{
			k_impl_dbgout.ischar = TRUE;
#if !_SB_MOBILE_
			k_impl_dbgout.fp = stdout;
#else
			k_impl_dbgout.fp = NULL;
#endif
		}
		else
		{
			k_impl_dbgout.ischar = FALSE;
			k_impl_dbgout.fp = fp;

			fseek(fp, 0, SEEK_END);
		}
	}
#endif

	DBG_UNLOCK;

	_k_dbg_console_char('\n', FALSE);

	//
	if (!k_impl_dbgout.ischar)
	{
		kDateTime dt;
		char sz[260];

		k_now(&dt);

		k_snprintf(sz, 260 - 1, "output redirect on %04d-%02d-%02d %02d:%02d:%02d.%03d", dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond);

		_k_dbg_console_trace(KLV_INFO, NULL, sz, TRUE);
	}
}


//////////////////////////////////////////////////////////////////////////
// 디버그 핸들링

#if _MSC_VER && _SB_WINDOWS_DESKTOP_
typedef BOOL(WINAPI* stub_MiniDumpFunc)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, CONST PMINIDUMP_EXCEPTION_INFORMATION, CONST PMINIDUMP_USER_STREAM_INFORMATION, CONST PMINIDUMP_CALLBACK_INFORMATION);

// 미니 덤프 함수 준비
static stub_MiniDumpFunc _k_dbg_prep_mini_dump_func(void)
{
	stub_MiniDumpFunc func = NULL;
	HMODULE dll = LoadLibrary(L"dbghelp");

	if (dll)
	{
		func = (stub_MiniDumpFunc)GetProcAddress(dll, "MiniDumpWriteDump");

		if (!func)
			FreeLibrary(dll);
	}

	return func;
}

// 미니 덤프
static LONG _stdcall _k_dbg_mini_dump_filter(PEXCEPTION_POINTERS e)
{
	stub_MiniDumpFunc mdfn;
	kwchar tmp[MAX_PATH];
	kwchar mdn[MAX_PATH];
	kDateTime dt;
	HANDLE process;
	HANDLE hf;
	DWORD dw;
	MINIDUMP_EXCEPTION_INFORMATION di;

	// 함수가 있으면 고고씽
	if ((mdfn = _k_dbg_prep_mini_dump_func()) != NULL)
	{
		k_now(&dt);
		k_snwprintf(tmp, MAX_PATH - 1, L".%08X%08X", dt.date, dt.time);

		GetModuleFileName(NULL, mdn, MAX_PATH - 1);
		k_wcscat(mdn, tmp);
		k_wcscat(mdn, L".dmp");

		process = GetCurrentProcess();
		dw = GetCurrentProcessId();
		hf = CreateFile(mdn, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

		di.ExceptionPointers = e;
		di.ClientPointers = FALSE;
		di.ThreadId = GetCurrentThreadId();

		mdfn(process, dw, hf, MiniDumpNormal, e ? &di : NULL, NULL, NULL);
	}

	// 예의상 멘트 날려 줌
	k_snwprintf(tmp, 260 - 1, L"Application error found!.\nPlease contact service center.\n\n[@=0x%08X]", e->ExceptionRecord->ExceptionCode);
	MessageBox(NULL, tmp, L"Application exception", MB_OK | MB_SETFOREGROUND);

	return EXCEPTION_EXECUTE_HANDLER;
}
#endif

/** 오류 발생 처리기를 활성화한다 (윈도우의 MiniDump) */
void k_apphandler(void)
{
	k_init();

#if _MSC_VER && _SB_WINDOWS_DESKTOP_
	SetUnhandledExceptionFilter(_k_dbg_mini_dump_filter);
#endif
}

// 시스템 오류 메시지를 가져온다.
static kcham _k_syserr_value(kint err, char* buf, ksize_t size, kcham isutf8)
{
#if _SB_WINDOWS_
	DWORD dw;
	kwchar uni[256];

	k_return_value_if_fail(buf != NULL, FALSE);

	dw = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
		(DWORD)err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), uni, 256 - 1, NULL);

	if (dw == 0)
		k_strncpy(buf, "unknown error", size);
	else
	{
		uni[dw - 1] = L'\0';
		k_wcsrem(uni, L"\n\r");

		if (isutf8)
			k_conv_utf16_to_utf8(buf, size, uni, 0);
		else
			k_getasc(buf, size, uni);
	}

	return TRUE;
#else
	char* psz;

	k_return_value_if_fail(buf != NULL, FALSE);

	psz = strerror(err);

	if (!psz)
		k_strncpy(buf, "unknown error", size);
	else
		k_strncpy(buf, psz, size);

	return TRUE;
#endif
}

/**
 * 시스템 오류를 가져온다.
 * @param [반환]	buf			(널값이 아닌) 버퍼.
 * @param	size			버퍼의 크기.
 * @param	code_or_zero	오류 코드. 0이면 시스템 오류로 처리.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_syserr(char* buf, ksize_t size, kint code_or_zero)
{
#if _SB_WINDOWS_
	return _k_syserr_value(code_or_zero > 0 ? code_or_zero : GetLastError(), buf, size, FALSE);
#else
	return _k_syserr_value(code_or_zero > 0 ? code_or_zero : errno, buf, size, FALSE);
#endif
}

/**
 * 시스템 오류, UTF-8 버전.
 * @param [반환]	buf			(널값이 아닌) 버퍼.
 * @param	size			버퍼의 크기.
 * @param	code_or_zero	오류 코드. 0이면 시스템 오류로 처리.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_syserr_utf8(char* buf, ksize_t size, kint code_or_zero)
{
#if _SB_WINDOWS_
	return _k_syserr_value(code_or_zero > 0 ? code_or_zero : GetLastError(), buf, size, TRUE);
#else
	return _k_syserr_value(code_or_zero > 0 ? code_or_zero : errno, buf, size, TRUE);
#endif
}
